package com.apinji.unionpay.service.serviceimpl;

import com.apinji.common.domain.ConsumeSmsLog;
import com.apinji.common.domain.TransactionRecord;
import com.apinji.common.domain.UserCardToken;
import com.apinji.common.domain.mapper.ConsumeSmsLogMapper;
import com.apinji.common.domain.mapper.TransactionRecordMapper;
import com.apinji.common.domain.mapper.UserCardTokenMapper;
import com.apinji.common.domain.wrapper.Reply;
import com.apinji.common.plugin.*;
import com.apinji.common.plugin.fastjson.JSONUtils;
import com.apinji.common.data.PayStatusEnum;
import com.apinji.common.data.ConsumeTypeEnum;
import com.apinji.common.data.PayChannelEnum;
import com.apinji.unionpay.common.UnionpayErrorCodeEnum;
import com.apinji.unionpay.dao.PayDao;
import com.apinji.unionpay.dao.TransactionRecordDao;
import com.apinji.unionpay.dto.ConsumeDto;
import com.apinji.unionpay.dto.ConsumeQueryDto;
import com.apinji.unionpay.dto.ConsumeSMSBackTokenDto;
import com.apinji.unionpay.service.PayService;
import com.apinji.unionpay.sdk.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import sun.rmi.runtime.Log;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

/**
 * Created by 轩辕夏夏 on 2017/7/4.
 */
@Service
public class PayServiceImpl implements PayService {

    @Autowired
    private UserCardTokenMapper userCardTokenMapper;

    @Autowired
    private ConsumeSmsLogMapper consumeSmsLogMapper;

    @Autowired
    private TransactionRecordMapper transactionRecordMapper;

    @Autowired
    private TransactionRecordDao transactionRecordDao;

    @Autowired
    private PayDao payDao;

    @Override
    public Reply consumeSMSBackToken(ConsumeSMSBackTokenDto consumeSMSBackTokenDto) {
        String cardNo = consumeSMSBackTokenDto.getCardNo();
        String userUUID = consumeSMSBackTokenDto.getUserUUID();
        String orderId = consumeSMSBackTokenDto.getOrderId();
        String phoneNo = consumeSMSBackTokenDto.getPhoneNo();
        int txnAmt = consumeSMSBackTokenDto.getTxnAmt();
        String outOrderId = consumeSMSBackTokenDto.getOutOrderId();

        String token = "";
        //1.根据银行卡号获取到已经开通绑定了的token信息
        UserCardToken userCardToken = userCardTokenMapper.getOneByCardNo(userUUID, cardNo);
        if(null != userCardToken && !StrUtils.isEmpty(userCardToken.getId())) {
            token = userCardToken.getToken();
        } else {
            return Mutual.notExist("服务器未查询到该卡号["+cardNo+"]的token绑定信息");
        }
        //
        Map<String, String> contentData = new HashMap<String, String>();

        /***银联全渠道系统，产品参数，除了encoding自行选择外其他不需修改***/
        contentData.put("version", SDKConfig.getConfig().getVersion());                   //版本号
        contentData.put("encoding", "UTF-8");            //字符集编码 可以使用UTF-8,GBK两种方式
        contentData.put("signMethod", SDKConfig.getConfig().getSignMethod()); //签名方法
        contentData.put("txnType", "77");                              //交易类型 11-代收
        contentData.put("txnSubType", "02");                           //交易子类型 02-消费短信
        contentData.put("bizType", "000902");                          //业务类型 认证支付2.0
        contentData.put("channelType", "07");                          //渠道类型07-PC

        /***商户接入参数***/
        contentData.put("merId", SDKConfig.getConfig().getMerId());    //商户号码（本商户号码仅做为测试调通交易使用，该商户号配置了需要对敏感信息加密）测试时请改成自己申请的商户号，【自己注册的测试777开头的商户号不支持代收产品】
        contentData.put("accessType", "0");                            //接入类型，商户接入固定填0，不需修改	
        contentData.put("orderId", outOrderId);             			   //商户订单号，8-40位数字字母，不能含“-”或“_”，可以自行定制规则
        String txnTime = DateUtils.getCurrentDateTime();
        contentData.put("txnTime", txnTime);    //订单发送时间，格式为YYYYMMDDhhmmss，必须取当前时间，否则会报txnTime无效
        String currencyCode = "156";
        contentData.put("currencyCode", currencyCode);						   //交易币种（境内商户一般是156 人民币）
        contentData.put("txnAmt", txnAmt+"");						   //交易金额，单位分，不要带小数点
        //contentData.put("accType", "01");                            //账号类型
        //contentData.put("accType", bankCardToken.getCardType()+"");    //账号类型
        String accType = "01";
        contentData.put("accType", accType);    //账号类型

        //送手机号码
        Map<String,String> customerInfoMap = new HashMap<String,String>();
        customerInfoMap.put("phoneNo", phoneNo);			        //手机号
        String customerInfoStr = AcpService.getCustomerInfoWithEncrypt(customerInfoMap,null,"UTF-8");

        contentData.put("customerInfo", customerInfoStr);
        contentData.put("encryptCertId", CertUtil.getEncryptCertId());       //加密证书的certId，配置在acp_sdk.properties文件 acpsdk.encryptCert.path属性下
        //消费短信：token号（从前台开通的后台通知中获取或者后台开通的返回报文中获取）
        String trId = SDKConfig.getConfig().getTrId();
        String tokenPayData = "{token="+token+"&trId="+trId+"}";
        contentData.put("tokenPayData", tokenPayData);

        //======================
        ConsumeSmsLog consumeSmsLog = new ConsumeSmsLog();
        String id = UUID.randomUUID().toString();
        consumeSmsLog.setId(id);
        consumeSmsLog.setUserUUID(userUUID);
        consumeSmsLog.setOrderId(orderId);
        consumeSmsLog.setCardNo(cardNo);
        consumeSmsLog.setPhoneNo(phoneNo);
        consumeSmsLog.setTxnAmt(txnAmt);
        consumeSmsLog.setToken(token);
        consumeSmsLog.setCreateTime(new Date());
        consumeSmsLog.setVersion(SDKConfig.getConfig().getVersion());
        consumeSmsLog.setMerId(SDKConfig.getConfig().getMerId());
        consumeSmsLog.setOutOrderId(outOrderId);
        consumeSmsLog.setTxnTime(txnTime);
        consumeSmsLog.setAccType(accType);
        consumeSmsLog.setCurrencyCode(currencyCode);
        consumeSmsLog.setTokenPayData(tokenPayData);
        consumeSmsLog.setStatus("create");
        consumeSmsLog.setMemo("");
        //
        consumeSmsLogMapper.addOneItem(consumeSmsLog);
        //======================



        /**对请求参数进行签名并发送http post请求，接收同步应答报文**/
        Map<String, String> reqData = AcpService.sign(contentData,"UTF-8");			 //报文中certId,signature的值是在signData方法中获取并自动赋值的，只要证书配置正确即可。
        String requestBackUrl = SDKConfig.getConfig().getBackRequestUrl();   								 //交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.backTransUrl
        Map<String, String> rspData = AcpService.post(reqData,requestBackUrl,"UTF-8"); //发送请求报文并接受同步应答（默认连接超时时间30秒，读取返回结果超时时间30秒）;这里调用signData之后，调用submitUrl之前不能对submitFromData中的键值对做任何修改，如果修改会导致验签不通过

        /**对应答码的处理，请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->**/
        //应答码规范参考open.unionpay.com帮助中心 下载  产品接口规范  《平台接入接口规范-第5部分-附录》
        if(!rspData.isEmpty()){
            if(AcpService.validate(rspData, "UTF-8")){
                LogUtil.writeLog("验证签名成功");
                String respCode = rspData.get("respCode") ;
                if(("00").equals(respCode)){
                    //成功
                    //TODO
                    consumeSmsLogMapper.updateLogStatus(id, "success", "验证码发送成功");
                    return Mutual.success();
                }else{
                    //其他应答码为失败请排查原因或做失败处理
                    //TODO
                    consumeSmsLogMapper.updateLogStatus(id, "failed", "验证码发送失败，银联返回错误码为(" + respCode+")，" + UnionpayErrorCodeEnum.getMsg(respCode));
                    return Mutual.unionpayError(respCode);
                }
            }else{
                LogUtil.writeErrorLog("验证签名失败");
                //TODO 检查验证签名失败的原因
                consumeSmsLogMapper.updateLogStatus(id, "failed", "验证签名失败");
                return Mutual.serverError("支付获取验证码，验证签名失败");
            }
        }else{
            //未返回正确的http状态
            LogUtil.writeErrorLog("未获取到返回报文或返回http状态码非200");
            consumeSmsLogMapper.updateLogStatus(id, "failed", "未获取到返回报文或返回http状态码非200");
            return Mutual.serverError("未获取到返回报文或返回http状态码非200");
        }
        //String reqMessage = DemoBase.genHtmlResult(reqData);
       // String rspMessage = DemoBase.genHtmlResult(rspData);
        //resp.getWriter().write("请求报文:<br/>"+reqMessage+"<br/>" + "应答报文:</br>"+rspMessage+"");
    }


    @Override
    public Reply consume(HttpServletRequest request, ConsumeDto consumeDto) {
        //
//        TransactionRecord transactionRecord = transactionRecordMapper.getOneItem(consumeDto.getOutOrderId());
//        if(null != transactionRecord && !StrUtils.isEmpty(transactionRecord.getId())) {
//            return Mutual.haveExist("该订单已经存在交易记录，请检查", transactionRecord);
//        }
        //
        String token = "";
        //1.根据银行卡号获取到已经开通绑定了的token信息
        UserCardToken userCardToken = userCardTokenMapper.getOneByCardNo(consumeDto.getUserUUID(), consumeDto.getCardNo());
        if(null != userCardToken && !StrUtils.isEmpty(userCardToken.getId())) {
            token = userCardToken.getToken();
        } else {
            return Mutual.notExist("服务器未查询到该卡号["+consumeDto.getCardNo()+"]的token绑定信息");
        }
        //
        String transactionRecordId = StrUtils.uuid();
        //
        Map<String, String> contentData = new HashMap<String, String>();

        /***银联全渠道系统，产品参数，除了encoding自行选择外其他不需修改***/
        contentData.put("version", SDKConfig.getConfig().getVersion());                  //版本号
        contentData.put("encoding", "UTF-8");                //字符集编码 可以使用UTF-8,GBK两种方式
        contentData.put("signMethod", SDKConfig.getConfig().getSignMethod()); //签名方法
        String txnType = "01";
        contentData.put("txnType", txnType);                              //交易类型 01-消费
        String txbSubType = "01";
        contentData.put("txnSubType", txbSubType);                           //交易子类型 01-消费
        String bizType = "000902";
        contentData.put("bizType", bizType);                          //业务类型 认证支付2.0
        String channelType = "07";
        contentData.put("channelType", channelType);                          //渠道类型07-PC

        /***商户接入参数***/
        contentData.put("merId", SDKConfig.getConfig().getMerId());    //商户号码（本商户号码仅做为测试调通交易使用，该商户号配置了需要对敏感信息加密）测试时请改成自己申请的商户号，【自己注册的测试777开头的商户号不支持代收产品】
        contentData.put("accessType", "0");                            //接入类型，商户接入固定填0，不需修改	
        //对外订单号，银联订单号
        //String outOrderId = UnionpayUtils.getOutOrderId();
        contentData.put("orderId", consumeDto.getOutOrderId());             			   //商户订单号，8-40位数字字母，不能含“-”或“_”，可以自行定制规则
        String txnTime = DateUtils.getCurrentDateTime();
        contentData.put("txnTime", txnTime);    //订单发送时间，格式为YYYYMMDDhhmmss，必须取当前时间，否则会报txnTime无效
        String currencyCode = "156";
        contentData.put("currencyCode", currencyCode);						   //交易币种（境内商户一般是156 人民币）
        contentData.put("txnAmt", consumeDto.getTxnAmt()+"");							   //交易金额，单位分，不要带小数点
        String accType = "01";
        contentData.put("accType", accType);                              //账号类型

        //后台通知地址（需设置为【外网】能访问 http https均可），支付成功后银联会自动将异步通知报文post到商户上送的该地址，失败的交易银联不会发送后台通知
        //后台通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  代收产品接口规范 代收交易 商户通知
        //注意:1.需设置为外网能访问，否则收不到通知    2.http https均可  3.收单后台通知后需要10秒内返回http200或302状态码 
        //    4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200，那么银联会间隔一段时间再次发送。总共发送5次，每次的间隔时间为0,1,2,4分钟。
        //    5.后台通知地址如果上送了带有？的参数，例如：http://abc/web?a=b&c=d 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签，否则将会验签失败
        String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();
        //String backUrl = basePath + "/v1.0/pay/backRcvResponse";
        LogUtil.writeMessage("consume backUrl-------->" + SDKConfig.getConfig().getBackPayUrl());
        contentData.put("backUrl", SDKConfig.getConfig().getBackPayUrl());
        String frontUrl = consumeDto.getFrontUrl();
        if(StrUtils.isEmpty(frontUrl)) {
            frontUrl = basePath + "/v1.0/pay/frontRcvResponse";
        }
        contentData.put("frontUrl", frontUrl);

        //消费：token号（从前台开通的后台通知中获取或者后台开通的返回报文中获取），验证码看业务配置(默认要短信验证码)。
        String trId = SDKConfig.getConfig().getTrId();
        String tokenPayData = "{token="+token+"&trId="+trId+"}";
        contentData.put("tokenPayData", tokenPayData);
        Map<String,String> customerInfoMap = new HashMap<String,String>();
        customerInfoMap.put("smsCode", consumeDto.getSmsCode());			    	//短信验证码
        //customerInfoMap不送pin的话 该方法可以不送 卡号
        String customerInfoStr = AcpService.getCustomerInfo(customerInfoMap,null,"UTF-8");
        contentData.put("customerInfo", customerInfoStr);

        // 请求方保留域，
        // 透传字段，查询、通知、对账文件中均会原样出现，如有需要请启用并修改自己希望透传的数据。
        // 出现部分特殊字符时可能影响解析，请按下面建议的方式填写：
        // 1. 如果能确定内容不会出现&={}[]"'等符号时，可以直接填写数据，建议的方法如下。
		contentData.put("reqReserved", transactionRecordId);
        // 2. 内容可能出现&={}[]"'符号时：
        // 1) 如果需要对账文件里能显示，可将字符替换成全角＆＝｛｝【】“‘字符（自己写代码，此处不演示）；
        // 2) 如果对账文件没有显示要求，可做一下base64（如下）。
        //    注意控制数据长度，实际传输的数据长度不能超过1024位。
        //    查询、通知等接口解析时使用new String(Base64.decodeBase64(reqReserved), "UTF-8");解base64后再对数据做后续解析。


        //分期付款用法（商户自行设计分期付款展示界面）：
        //修改txnSubType=03，增加instalTransInfo域
        //【测试环境】固定使用测试卡号6221558812340000，测试金额固定在100-1000元之间，分期数固定填06
        //【生产环境】支持的银行列表清单请联系银联业务运营接口人索要
        //contentData.put("txnSubType", "03");                           //交易子类型 03-分期付款
        //contentData.put("instalTransInfo","{numberOfInstallments=06}");//分期付款信息域，numberOfInstallments期数

        /**对请求参数进行签名并发送http post请求，接收同步应答报文**/
        Map<String, String> reqData = AcpService.sign(contentData,"UTF-8");				//报文中certId,signature的值是在signData方法中获取并自动赋值的，只要证书配置正确即可。
        String requestBackUrl = SDKConfig.getConfig().getBackRequestUrl();   							//交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.backTransUrl
        Map<String, String> rspData = AcpService.post(reqData,requestBackUrl,"UTF-8");	//发送请求报文并接受同步应答（默认连接超时时间30秒，读取返回结果超时时间30秒）;这里调用signData之后，调用submitUrl之前不能对submitFromData中的键值对做任何修改，如果修改会导致验签不通过


        //======================
        TransactionRecord transactionRecord = new TransactionRecord();
        transactionRecord.setId(transactionRecordId);
        transactionRecord.setUserUUID(consumeDto.getUserUUID());
        transactionRecord.setCreateTime(new Date());
        transactionRecord.setPayStatus(PayStatusEnum.ORDER_CREATE.getStatus());
        transactionRecord.setCardType(userCardToken.getCardType());
        transactionRecord.setCardNo(consumeDto.getCardNo());
        transactionRecord.setOrderId(consumeDto.getOrderId());
        transactionRecord.setOrderName(consumeDto.getOrderName());
        transactionRecord.setOrderDesc(consumeDto.getOrderDesc());
        transactionRecord.setFrontUrl(frontUrl);
        transactionRecord.setConsumeType(ConsumeTypeEnum.PAYMENT_TYPE.getCode());
        transactionRecord.setConsumeTypeName(ConsumeTypeEnum.PAYMENT_TYPE.getName());

        transactionRecord.setOutOrderId(consumeDto.getOutOrderId());//对外的订单编号 UUID
        transactionRecord.setPayChannel(PayChannelEnum.UNIONPAY_CHANNEL.getCode());

        transactionRecord.setTxnType(txnType);
        transactionRecord.setTxnSubType(txbSubType);
        transactionRecord.setBizType(bizType);
        transactionRecord.setChannelType(channelType);//渠道类型
        transactionRecord.setMerId(SDKConfig.getConfig().getMerId());
        transactionRecord.setTxnTime(txnTime);//订单发送时间
        transactionRecord.setTxnAmt(consumeDto.getTxnAmt());//订单金额，单位 分，不含小数点
        transactionRecord.setCurrencyCode(currencyCode);//交易币种
        transactionRecord.setToken(token);
        transactionRecord.setTokenPayData(tokenPayData);
        transactionRecord.setSmsCode(consumeDto.getSmsCode());

        transactionRecord.setMemo(PayStatusEnum.ORDER_CREATE.getName());//1
        //
        transactionRecordMapper.addTransactionRecord(transactionRecord);
        //======================


        /**对应答码的处理，请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->**/
        //应答码规范参考open.unionpay.com帮助中心 下载  产品接口规范  《平台接入接口规范-第5部分-附录》
        //StringBuffer parseStr = new StringBuffer("");
        if(!rspData.isEmpty()){
            if(AcpService.validate(rspData, "UTF-8")){
                LogUtil.writeLog("验证签名成功");
                String respCode = rspData.get("respCode") ;
                if(("00").equals(respCode)){
                    //交易已受理(不代表交易已成功），等待接收后台通知更新订单状态,也可以主动发起 查询交易确定交易状态。
                    //TODO
                    //如果是配置了敏感信息加密，如果需要获取卡号的铭文，可以按以下方法解密卡号
                    //String accNo1 = rspData.get("accNo");
//					String accNo2 = AcpService.decryptData(accNo1, "UTF-8");  //解密卡号使用的证书是商户签名私钥证书acpsdk.signCert.path
//					LogUtil.writeLog("解密后的卡号："+accNo2);
//					//parseStr.append("解密后的卡号："+accNo2);
                    //transactionRecordMapper.updatePayStatus(transactionRecordId, PayStatusEnum.BANK_DEAL.getStatus(), PayStatusEnum.BANK_CALLBACK_VALIDATE_SUCCESS.getName());
                    transactionRecordDao.updatePayStatus(transactionRecordId, PayStatusEnum.BANK_DEAL.getStatus(), PayStatusEnum.BANK_DEAL.getName());
                    //
                    return Mutual.success(transactionRecord);
                }else if(("03").equals(respCode)||
                        ("04").equals(respCode)||
                        ("05").equals(respCode)){
                    //后续需发起交易状态查询交易确定交易状态
                    //TODO
                    //return Mutual.serverError("后续需发起交易状态查询交易确定交易状态");
                    return Mutual.unionpayError(respCode);
                }else{
                    //其他应答码为失败请排查原因或做失败处理
                    //transactionRecordMapper.updatePayStatus(transactionRecordId, PayStatusEnum.FAILD_DEAL.getStatus(), "支付失败，银联返回错误码为(" + respCode+")，" + UnionpayErrorCodeEnum.getMsg(respCode));
                    transactionRecordDao.updatePayStatus(transactionRecordId, PayStatusEnum.FAILD_DEAL.getStatus(), "支付失败，银联返回错误码为(" + respCode+")，" + UnionpayErrorCodeEnum.getMsg(respCode));
                    return Mutual.unionpayError(respCode);
                }
            }else{
                LogUtil.writeErrorLog("验证签名失败");
                transactionRecordDao.updatePayStatus(transactionRecordId, PayStatusEnum.FAILD_DEAL.getStatus(), "验证签名失败");
                return Mutual.serverError("验证签名失败");
            }
        }else{
            //未返回正确的http状态
            LogUtil.writeErrorLog("未获取到返回报文或返回http状态码非200");
            transactionRecordDao.updatePayStatus(transactionRecordId, PayStatusEnum.FAILD_DEAL.getStatus(), "未获取到返回报文或返回http状态码非200");
            return Mutual.serverError("未获取到返回报文或返回http状态码非200");
        }
        //String reqMessage = DemoBase.genHtmlResult(reqData);
        //String rspMessage = DemoBase.genHtmlResult(rspData);
        //resp.getWriter().write("请求报文:<br/>"+reqMessage+"<br/>" + "应答报文:</br>"+rspMessage+parseStr);
    }


    /**
     * 1.先查询数据库状态   和   查询银联返回状态            如果都为成功    直接返回成功
     * 2.银联返回成功           更新数据库状态为  成功
     * 3.银联返回失败      更新数据库为   失败
     * @param consumeQueryDto
     * @return
     */
    @Override
    public Reply consumeQuery(ConsumeQueryDto consumeQueryDto) {
        String id = consumeQueryDto.getId();
        String userUUID = consumeQueryDto.getUserUUID();
        //数据库状态
        TransactionRecord transactionRecord = transactionRecordMapper.getOneItemByUser(id, userUUID);
        //银联返回状态
        Reply reply = payDao.consumeQuery(id, userUUID);

        if(null == transactionRecord || StrUtils.isEmpty(transactionRecord.getId())) {
            return Mutual.notExist("交易记录不存在");
        }

        //1.都为成功时，直接返回成功
        if(transactionRecord.getPayStatus() == PayStatusEnum.SUCCESS_DEAL.getStatus() && reply.getSuccess()) {
            return Mutual.success(transactionRecord);
        }
        //2.银联返回成功
        if(reply.getSuccess()) {
            transactionRecordMapper.updatePayStatus(id, PayStatusEnum.SUCCESS_DEAL.getStatus(), PayStatusEnum.SUCCESS_DEAL.getName());
            return Mutual.success(transactionRecord);
        }
        //3.银联返回失败
        if(!reply.getSuccess()) {
            transactionRecordMapper.updatePayStatus(id, PayStatusEnum.FAILD_DEAL.getStatus(), PayStatusEnum.FAILD_DEAL.getName());
            reply.setData(transactionRecord);
            return reply;
        }
        //
        return Mutual.serverError("银联查询返回失败或者数据库状态为失败");
    }

    @Override
    public Reply consumeQueryUni(ConsumeQueryDto consumeQueryDto) {
        Reply reply = payDao.consumeQuery(consumeQueryDto.getId(), consumeQueryDto.getUserUUID());
        //
        return reply;
    }

    @Override
    public void backRcvResponse(HttpServletRequest req, HttpServletResponse res) throws Exception {
        LogUtil.writeLog("BackRcvResponse接收后台通知开始");

        String encoding = req.getParameter(SDKConstants.param_encoding);
        // 获取银联通知服务器发送的后台通知参数
        Map<String, String> reqParam = UnionpayUtils.getAllRequestParam(req);

        LogUtil.printRequestLog(reqParam);

        Map<String, String> valideData = null;
        if (null != reqParam && !reqParam.isEmpty()) {
            Iterator<Map.Entry<String, String>> it = reqParam.entrySet().iterator();
            valideData = new HashMap<String, String>(reqParam.size());
            while (it.hasNext()) {
                Map.Entry<String, String> e = it.next();
                String key = (String) e.getKey();
                String value = (String) e.getValue();

                valideData.put(key, value);
            }
        }

        String transactionRecordId = valideData.get("reqReserved");
        //
        if(StrUtils.isEmpty(transactionRecordId)) {
            LogUtil.writeLog("银联回调transactionRecordId为空");
            return;
        }

        TransactionRecord transactionRecord = transactionRecordMapper.getOneById(transactionRecordId);
        if(null != transactionRecord && transactionRecord.getPayStatus() == PayStatusEnum.SUCCESS_DEAL.getStatus()) {
            LogUtil.writeLog("银联已经成功回调，且状态已经为成功");
            //返回给银联服务器http 200状态码
            res.getWriter().print("ok");
            return;
        }
        //
        if(null == transactionRecord) {
            LogUtil.writeLog("ID为："+transactionRecordId+"的交易记录不存在");
            return;
        }

        transactionRecordDao.updatePayStatus(transactionRecordId, PayStatusEnum.CALLBACK_ONLY.getStatus(), PayStatusEnum.CALLBACK_ONLY.getName());

        //重要！验证签名前不要修改reqParam中的键值对的内容，否则会验签不过
        if (!AcpService.validate(valideData, encoding)) {
            LogUtil.writeLog("验证签名结果[失败].");
            //验签失败，需解决验签问题
            transactionRecordDao.updatePayStatus(transactionRecordId, PayStatusEnum.CALLBACK_VALIDATE_FAIL.getStatus(), PayStatusEnum.CALLBACK_VALIDATE_FAIL.getName());
        } else {
            LogUtil.writeLog("验证签名结果[成功].");
            //
            transactionRecordDao.updatePayStatus(transactionRecordId, PayStatusEnum.CALLBACK_VALIDATE_SUCCESS.getStatus(), PayStatusEnum.CALLBACK_VALIDATE_SUCCESS.getName());

            //补充银联回调信息
            String queryId = valideData.get("queryId"); //查询流水号   由银联返回，用于在后续类交易中唯一标识一笔交易
            String traceNo = valideData.get("traceNo"); //系统跟踪号   收单机构对账时使用，该域由银联系统产生
            String traceTime = valideData.get("traceTime");//交易传输时间   （月月日日时时分分秒秒）24小时制收单机构对账时使用，该域由银联系统产生
            String settleDate = valideData.get("settleDate");//清算日期   为银联和入网机构间的交易结算日期。一般前一日23点至当天23点为一个清算日。也就是23点前的交易，当天23点之后开始结算，23点之后的交易，要第二天23点之后才会结算。测试环境为测试需要，23:30左右日切，所以23:30到23:30为一个清算日，当天23:30之后为下个清算日。
            String settleCurrencyCode = valideData.get("settleCurrencyCode");//清算币种   境内返回156
            String settleAmt = valideData.get("settleAmt");//清算金额    取值同交易金额
            String respMsg = valideData.get("respMsg");//应答信息
            transactionRecordMapper.updateCallBackInfo(transactionRecordId, queryId, traceNo, traceTime, settleDate, settleCurrencyCode, settleAmt, respMsg);

//            String customerInfo = valideData.get("customerInfo");
//            if(null!=customerInfo){
//                Map<String,String>  customerInfoMap = AcpService.parseCustomerInfo(customerInfo, "UTF-8");
//                LogUtil.writeLog("customerInfoMap明文: "+ customerInfoMap);
//            }

            //String accNo = valideData.get("accNo");
            //如果配置了敏感信息加密证书，可以用以下方法解密，如果不加密可以不需要解密
//            if(null!=accNo){
//                accNo = AcpService.decryptData(accNo, "UTF-8");
//                LogUtil.writeLog("accNo明文: "+ accNo);
//            }

//            String tokenPayData = valideData.get("tokenPayData");
//            if(null!=tokenPayData){
//                Map<String,String> tokenPayDataMap = SDKUtil.parseQString(tokenPayData.substring(1, tokenPayData.length() - 1));
//                String token = tokenPayDataMap.get("token");//这样取
//                LogUtil.writeLog("tokenPayDataMap明文: " + tokenPayDataMap);
//            }

            String respCode = valideData.get("respCode");
            //判断respCode=00、A6后，对涉及资金类的交易，请再发起查询接口查询，确定交易成功后更新数据库。
            if("00".equals(respCode)) {
                //再次发起查询，确定交易成功
                Reply reply = payDao.consumeQuery(transactionRecord.getId(), transactionRecord.getUserUUID());
                if(!reply.getSuccess()) {
                    transactionRecordDao.updatePayStatus(transactionRecordId, PayStatusEnum.FAILD_DEAL.getStatus(), PayStatusEnum.FAILD_DEAL.getName()+"：再次发起查询，返回失败的结果");
                    return;
                }
                //
                transactionRecord = transactionRecordMapper.getOneById(transactionRecordId);
                String userUUID = "";
                String frontUrl = "";
                if(null != transactionRecord) {
                    userUUID = transactionRecord.getUserUUID();
                    frontUrl = transactionRecord.getFrontUrl();
                }
                if(StrUtils.isEmpty(userUUID)) {
                    return;
                }
                //
                LogUtil.writeLog("userUUID: " + userUUID + "      transactionRecordId：" + transactionRecordId + "         frontUrl：" + frontUrl);
                if(null != transactionRecord && transactionRecord.getPayStatus() != PayStatusEnum.SUCCESS_DEAL.getStatus()) {
                    transactionRecordDao.updatePayStatus(transactionRecordId, PayStatusEnum.SUCCESS_DEAL.getStatus(), PayStatusEnum.SUCCESS_DEAL.getName());
                }
                //
                transactionRecord = transactionRecordMapper.getOneById(transactionRecordId);
                //
                LogUtil.writeLog("开始回调参数给前端：" + frontUrl);
                LogUtil.writeLog("开始回调参数给前端：" + JSONUtils.parseJsonStr(transactionRecord));
                String result = HttpUtils.post(frontUrl, JSONUtils.toJsonObject(transactionRecord));
                LogUtil.writeLog("前端回调返回：" + JSONUtils.parseJsonStr(transactionRecord));
                //
                if(null != result && result.equalsIgnoreCase("SUCCESS")) {
                    transactionRecordMapper.updateFrontRspStatus(transactionRecordId, "200");
                }
                //返回给银联服务器http 200状态码
                res.getWriter().print("ok");
            }
        }
        LogUtil.writeLog("BackRcvResponse接收后台通知结束");
    }

    @Override
    public String frontRcvResponse(HttpServletRequest req, HttpServletResponse res) throws Exception {
        LogUtil.writeLog("FrontRcvResponse前台接收报文返回开始");

        String encoding = req.getParameter(SDKConstants.param_encoding);
        LogUtil.writeLog("返回报文中encoding=[" + encoding + "]");
        Map<String, String> respParam = UnionpayUtils.getAllRequestParam(req);

        // 打印请求报文
        LogUtil.printRequestLog(respParam);

        Map<String, String> valideData = null;
        StringBuffer page = new StringBuffer();
        if (null != respParam && !respParam.isEmpty()) {
            Iterator<Map.Entry<String, String>> it = respParam.entrySet()
                    .iterator();
            valideData = new HashMap<String, String>(respParam.size());
            while (it.hasNext()) {
                Map.Entry<String, String> e = it.next();
                String key = (String) e.getKey();
                String value = (String) e.getValue();

                page.append("<tr><td width=\"30%\" align=\"right\">" + key
                        + "(" + key + ")</td><td>" + value + "</td></tr>");
                valideData.put(key, value);
            }
        }
        if (!AcpService.validate(valideData, encoding)) {
            page.append("<tr><td width=\"30%\" align=\"right\">验证签名结果</td><td>失败</td></tr>");
            LogUtil.writeLog("验证签名结果[失败].");
        } else {
            page.append("<tr><td width=\"30%\" align=\"right\">验证签名结果</td><td>成功</td></tr>");
            LogUtil.writeLog("验证签名结果[成功].");
            System.out.println(valideData.get("orderId")); //其他字段也可用类似方式获取

            //如果是获取token号的交易，解析tokenPayData域
            String customerInfo = valideData.get("customerInfo");
            if(null!=customerInfo){
                Map<String,String>  customerInfoMap = AcpService.parseCustomerInfo(customerInfo, "UTF-8");
                page.append("customerInfo明文: "+customerInfoMap + "<br>");
            }

//			String accNo = valideData.get("accNo");
//			//如果返回的卡号是密文那么，可以用下边方法解密,如果不是密文可以不用解密
//			if(null!= accNo){
//				accNo = AcpService.decryptData(accNo, "UTF-8");
//				page.append("<br>accNo明文: "+accNo);
//			}

            String tokenPayData = valideData.get("tokenPayData");
            if(null!=tokenPayData){
                Map<String,String> tokenPayDataMap = SDKUtil.parseQString(tokenPayData.substring(1, tokenPayData.length() - 1));
                String token = tokenPayDataMap.get("token");//这样取
                page.append("tokenPayDataMap明文: " + tokenPayDataMap + "<br>");
            }

            String respCode = valideData.get("respCode");
            //判断respCode=00、A6后，对涉及资金类的交易，请再发起查询接口查询，确定交易成功后更新数据库。
        }
        //
        LogUtil.writeLog("FrontRcvResponse前台接收报文返回结束");
        //
        return page.toString();
    }

















    //测试============================================================
    @Override
    public String testPostData(String transactionRecordId, String postUrl) {
        TransactionRecord transactionRecord = transactionRecordMapper.getOneById(transactionRecordId);
        //
        LogUtil.writeLog("开始回调参数给前端：" + postUrl);
        LogUtil.writeLog("开始回调参数给前端：" + JSONUtils.parseJsonStr(transactionRecord));
        String result = HttpUtils.post(postUrl, JSONUtils.toJsonObject(transactionRecord));
        LogUtil.writeLog("前端回调返回：" + result);
        return result;
    }
}
